#include <stdio.h>
#include <string.h>
#include <float.h>
#include "Scene/Map.h"
#include "MapMgr.h"
#include "base/core/com_define.h"
#include "base/core/lua_parser.h"
#include "base/core/os/file.h"
#include "base/core/os/string.h"
#include "base/core/json_parser.h"
#include "base/core/gamemath.h"
#include "RecastDemo/Include/MeshHeaderDef.h"
#include "Detour/Include/DetourCommon.h"
#include "base/core/char_operation.h"
#include "Common/CharacterDefine.h"
#include "Common/Utility/RecastUtility.h"
#include "ProtoBuffer/AllProtocol.h"
//#include "AllProtocol.h"

#include "Character/monster/MonsterRefCfg.h"
#include "Common/TableData/AreaAreaCfg.h"
#ifdef _MSC_VER
#pragma warning(disable:4616)
#pragma warning(disable:4244)
#endif


static const char     s_configFile[] = "./data/map/";

Map::Map()
{
    m_navMesh = NULL;
    m_navMeshQuery = NULL;
	m_pMapCfg = nullptr;
    memset(m_name, 0, sizeof(m_name));
    memset(m_pathPolys, 0, sizeof(m_pathPolys));
    memset(m_straightPathPoints, 0, sizeof(m_straightPathPoints));
}

Map::~Map()
{
}

bool Map::Init(uint32_t Id)
{
    bool result  = false;
    bool retCode = false;

	m_pMapCfg = g_GetMapMapCfgTable()->GetMapMapCfgInfo(Id);
	if (nullptr == m_pMapCfg)
	{
		MMOLOG_FMT_ERROR(" nullptr == m_pMapCfg....Id:%u ", Id);
		MMO_PROCESS_ERROR(false);
	}
	retCode = _LoadNavMesh(m_pMapCfg->mapResources.c_str());
	MMOLOG_PROCESS_ERROR(retCode);

	//m_normalFilter.setExcludeFlags(SAMPLE_POLYFLAGS_DISABLED);
	//m_normalFilter.setIncludeFlags(SAMPLE_POLYFLAGS_WALK | SAMPLE_POLYFLAGS_DISABLED);

	//关闭所有行走开发
	m_normalFilter.setIncludeFlags(0);

	//开启行走开发
	m_normalFilter.setIncludeFlags(m_normalFilter.getIncludeFlags() | SAMPLE_POLYFLAGS_WALK);

	//关闭执行开关
	m_normalFilter.setExcludeFlags(0);

    result = true;
Exit0:
    return result;
}

void Map::UnInit()
{
}

bool Map::_LoadNavMesh(const char* name) 
{
    bool result = false;
    bool retCode = false;

    char path[256];
    dtStatus status;

	snprintf(path, sizeof(path), "%s%s.bytes", s_configFile, name);

    MMO_FREE_NAVMESHQUERY(m_navMeshQuery);
	MMO_FREE_NAVMESH(m_navMesh);

	std::string str(name);
	navMeshInfo *pNav = g_GetMapMgr()->GetNavMeshInfo(str);
	if (!pNav)
	{
		retCode = g_LoadNavMeshByJsonFile(path, &m_navMesh);
		if (!retCode)
		{
			MMOLOG_FMT_ERROR(" g_LoadNavMeshByJsonFile failed....path:%s ", str.c_str());
			MMO_PROCESS_ERROR(false);
		}
		m_navMeshQuery = dtAllocNavMeshQuery();
		if (nullptr == m_navMeshQuery)
		{
			MMOLOG_FMT_ERROR(" dtAllocNavMeshQuery failed....path:%s ", str.c_str());
			MMO_PROCESS_ERROR(false);
		}
		//MMOLOG_ASSERT_EXIT(m_navMeshQuery != NULL);

		status = m_navMeshQuery->init(m_navMesh, MAX_NAV_POLYS);
		MMOLOG_ASSERT_EXIT(dtStatusSucceed(status));

		navMeshInfo navInfo;
		navInfo.pdtNavMesh = m_navMesh;
		navInfo.pdtNavMeshQuery = m_navMeshQuery;
		g_GetMapMgr()->AddNavMeshInfo(str, navInfo);
	}
	else
	{
		m_navMesh = pNav->pdtNavMesh;
		m_navMeshQuery = pNav->pdtNavMeshQuery;
	}

    result = true;
Exit0:
    if (!result) 
	{
		MMO_FREE_NAVMESHQUERY(m_navMeshQuery);
        MMO_FREE_NAVMESH(m_navMesh);
    }
    return result;
}

bool Map::GetNavPath(const float* srcPos, const float* dstPos, VEC_POINT3& path, VEC_FLAGS& flgs) 
{
    MMO_ASSERT(srcPos != nullptr);
	MMO_ASSERT(dstPos != nullptr);

    bool result = false;
    bool retCode = false;

    int nPolys = 0;
    int nStraightPoints = 0;
    dtStatus status;

    float nearStartPos[3];
    float nearEndPos[3];

    dtPolyRef startRef = INVALID_NAVMESH_POLYREF;
    dtPolyRef endRef = INVALID_NAVMESH_POLYREF;

    unsigned char straightPathFlags[MAX_NAV_POLYS];
    dtPolyRef straightPathPolys[MAX_NAV_POLYS];

    MMO_PROCESS_ERROR(m_navMesh);
	MMO_PROCESS_ERROR(m_navMeshQuery);

    retCode = FindNearestPos(srcPos[0], srcPos[2], srcPos[1], &nearStartPos[0], &nearStartPos[2], &nearStartPos[1], &startRef);
	MMO_PROCESS_ERROR(retCode);

    retCode = FindNearestPos(dstPos[0], dstPos[2], dstPos[1], &nearEndPos[0], &nearEndPos[2], &nearEndPos[1], &endRef);
	MMO_PROCESS_ERROR(retCode);

    status = m_navMeshQuery->findPath(startRef, endRef, nearStartPos, nearEndPos, &m_normalFilter, m_pathPolys, &nPolys, MAX_NAV_POLYS);
	//MMO_PROCESS_ERROR(dtStatusSucceed(status) && !dtStatusSucceed(DT_PARTIAL_RESULT));
	MMO_PROCESS_ERROR(dtStatusSucceed(status) && (status & DT_PARTIAL_RESULT) == 0);
	MMO_PROCESS_ERROR(nPolys > 0);
	//for (size_t i = 0; i < nPolys; i++)
	//{
	//	printf("%d\n", m_pathPolys[i]);
	//}

	//fix add by lian In case of partial path, make sure the end point is clamped to the last polygon.
	float epos[3];
	dtVcopy(epos, nearEndPos);
	if (m_pathPolys[nPolys - 1] != endRef)
	{
		m_navMeshQuery->closestPointOnPoly(m_pathPolys[nPolys - 1], nearEndPos, epos, 0);
	}

	status = m_navMeshQuery->findStraightPath(nearStartPos, epos, m_pathPolys, nPolys, m_straightPathPoints, straightPathFlags, straightPathPolys, &nStraightPoints, MAX_NAV_POLYS);
	MMO_PROCESS_ERROR(dtStatusSucceed(status));

    path.clear();
    flgs.clear();
    path.resize(nStraightPoints);
    flgs.resize(nStraightPoints);

    for (int i = 0, j = 0; i < nStraightPoints * 3; i += 3, j++)
	{
        path[j].x = m_straightPathPoints[i + 0];
        path[j].y = m_straightPathPoints[i + 1];
        path[j].z = m_straightPathPoints[i + 2];
        flgs[j]   = straightPathFlags[j];
    }


    result = true;
Exit0:
    return result;
}

bool Map::GetIntNavPath(const float* srcPos, const float* dstPos, VEC_PATH& IntPath)
{
	bool result = false;
	bool retCode = false;

	VEC_POINT3 path = {};
	VEC_FLAGS flgs = {};
	Point3<uint32_t> pathPoint;

	retCode = GetNavPath(srcPos, dstPos, path, flgs);
	MMO_PROCESS_ERROR(retCode);

	for (size_t i = 0; i < path.size(); i++)
	{
		pathPoint.x = uint32_t(path[i].x * 1000);
		pathPoint.y = uint32_t(path[i].y * 1000);
		pathPoint.z = uint32_t(path[i].z * 1000);
		IntPath.push_back(pathPoint);
	}

	result = true;
Exit0:
	return result;
}

// 获取忽略高度值的最近poly，返回值的nearestPt带有高度
dtPolyRef Map::_GetNearestPoly2D(float posX, float posY, float* extents, float* nearestPt) 
{
	MMO_ASSERT(extents != nullptr);

    dtPolyRef nearestRef = INVALID_NAVMESH_POLYREF;
    float center[3];
    dtPolyRef nearest = 0;
    float nearestDistanceSqr = FLT_MAX;
    float maxHeight = 0.0f;

    extents[1] = 2000.0f;
    dtVset(center, posX, 0.0f, posY);

    dtPolyRef polys[128];
    int polyCount = 0;
    if (dtStatusFailed(m_navMeshQuery->queryPolygons(center, extents, &m_normalFilter, polys, &polyCount, 128)))
        goto Exit0;

    // Find nearest polygon amongst the nearby polygons.
    nearest = 0;
    nearestDistanceSqr = FLT_MAX;

    for (int i = 0; i < polyCount; ++i) 
	{
        dtPolyRef ref = polys[i];
        float closestPtPoly[3];
        float closetPtPoly2D[3];
        float diff[3];
        bool posOverPoly = false;
        float d = 0;
        m_navMeshQuery->closestPointOnPoly(ref, center, closestPtPoly, &posOverPoly);

        // 不计入高度
        dtVset(closetPtPoly2D, closestPtPoly[0], 0.0f, closestPtPoly[2]);

        // If a point is directly over a polygon and closer than
        // climb height, favor that instead of straight line nearest point.
        dtVsub(diff, center, closetPtPoly2D);
        if (posOverPoly) 
		{
            const dtMeshTile* tile = 0;
            const dtPoly* poly = 0;
            m_navMesh->getTileAndPolyByRefUnsafe(polys[i], &tile, &poly);
            d = dtAbs(diff[1]) - tile->header->walkableClimb;
            d = d > 0 ? d*d : 0;
        }
        else 
		{
            d = dtVlenSqr(diff);
        }

        if (d < nearestDistanceSqr || (d == nearestDistanceSqr && maxHeight < closestPtPoly[1])) 
		{
            if (nearestPt) 
			{
                dtVcopy(nearestPt, closestPtPoly);
                maxHeight = closestPtPoly[1];
            }
            nearestDistanceSqr = d;
            nearest = ref;
        }
    }
    nearestRef = nearest;

Exit0:
    return nearestRef;
}

dtPolyRef Map::GetNearestPoly(float posX, float posY, float posH, float* extents, float* nearPoint) 
{
	MMO_ASSERT(extents != nullptr);

    dtPolyRef polyRef = INVALID_NAVMESH_POLYREF;
    float startPos[3];

    extents[1] = 2000.0f;
    dtVset(startPos, posX, posH, posY);

    // 使用重写的忽略高度的最近poly查找函数
    if (posH == 0.0f) 
	{
        return _GetNearestPoly2D(posX, posY, extents, nearPoint);
    }

    m_navMeshQuery->findNearestPoly(startPos, extents, &m_normalFilter, &polyRef, nearPoint);
    return polyRef;
}

bool Map::FindNearestPos(float posX, float posY, float posH, float* rsPosX, float* rsPosY, float* rsPosH, dtPolyRef* rsPolyRef) 
{
    bool result = false;

    dtPolyRef polyRef = INVALID_NAVMESH_POLYREF;
    float extents[3] = {0.5f, 4.0f, 0.5f};
    float nearPoint[3] = {0.0f};

    polyRef = GetNearestPoly(posX, posY, posH, extents, nearPoint);
	MMO_PROCESS_ERROR(polyRef != INVALID_NAVMESH_POLYREF);

    if (rsPosX != nullptr) 
	{
        *rsPosX = nearPoint[0];
    }
    if (rsPosY != nullptr) 
	{
        *rsPosY = nearPoint[2];
    }
    if (rsPosH != nullptr) 
	{
        *rsPosH = nearPoint[1];
    }
    if (rsPolyRef != nullptr) 
	{
        *rsPolyRef = polyRef;
    }
    result = true;
Exit0:
    return result;
}

bool Map::Raycast(float* startPos, float dir, float length, HitWallParam* hitParam) 
{
	MMO_ASSERT(startPos != nullptr);
	MMO_ASSERT(hitParam != nullptr);
    bool result = false;
    bool retCode = false;

    dtPolyRef startRef = INVALID_NAVMESH_POLYREF;
    dtStatus status;
    float nearPos[3] = {0.0f};
    float endPos[3] = {0.0f};
    float hitNormal[3] = {0.0f};
    float t = 0.0f;
    int polyCount = 0;
    float radian = 0.0f;
    Point2<float> offsetLength;

    radian = AngleToRadian(ReviseDir(dir));
    FormulaPos(radian, length, &offsetLength);

    dtVset(endPos, startPos[0] + offsetLength.x, 0.0f, startPos[2] + offsetLength.y);

    retCode = FindNearestPos(startPos[0], startPos[2], startPos[1], &nearPos[0], &nearPos[2], &nearPos[1], &startRef);
	MMOLOG_PROCESS_ERROR(retCode);

    status = m_navMeshQuery->raycast(startRef, nearPos, endPos, &m_normalFilter,
        &t, hitNormal, m_pathPolys, &polyCount, MAX_NAV_POLYS);

	MMOLOG_PROCESS_ERROR(dtStatusSucceed(status));

    hitParam->pathPolys = m_pathPolys;
    hitParam->polyCount = polyCount;

    if (t > 1.0f) 
	{
        dtVcopy(hitParam->hitPos, endPos);
        hitParam->hitPos[1] = startPos[1];

        hitParam->hitLength  = 0.0f;
        hitParam->tangentDir = 0.0f;

		MMO_ASSERT(polyCount > 0);

        if (polyCount > 0) 
		{
            hitParam->hitRef = m_pathPolys[polyCount - 1];
            GetPolyHeight(hitParam->hitRef, hitParam->hitPos[0], hitParam->hitPos[2], &hitParam->hitPos[1]);
        }

        goto Exit1;
    }

    if (t > 0.01 && t < 1.0f) 
	{
        dtVlerp(hitParam->hitPos, nearPos, endPos, t);
        hitParam->hitPos[1] = startPos[1];
        hitParam->hitLength = (1 - t) * length;

		MMO_ASSERT(polyCount > 0);

        if (polyCount > 0) 
		{
            hitParam->hitRef = m_pathPolys[polyCount - 1];
            GetPolyHeight(hitParam->hitRef, hitParam->hitPos[0], hitParam->hitPos[2], &hitParam->hitPos[1]);
        }
    }
    else 
	{
        dtVcopy(hitParam->hitPos, startPos);
        hitParam->hitLength = length;
        hitParam->hitRef = startRef;
    }

    // 计算碰撞切线方向
    if (t > 0.0f) 
	{
        float tangenDir = 0.0f;
		Point2<float> from = { 0.0f, 0.0f };
		Point2<float> to = { hitNormal[0], hitNormal[2] };
		float normalDir = Angle2D(from, to); // 碰撞法线  
        float detaDir = DeltaAngle(normalDir, dir); // 当前方向和法线的夹角
        
        if (detaDir > 90.0f && detaDir <= 180.0f) 
		{
            tangenDir = ReviseDir(dir - (detaDir - 90.0f) - 2.0f);
        }
        else if (detaDir > 180.0f && detaDir <= 270.0f) 
		{
            tangenDir = ReviseDir(dir + (270.0f - detaDir) + 2.0f);
        }

        hitParam->tangentDir = tangenDir;
    }

Exit1:
    result = true;
Exit0:
    return result;
}

bool Map::GetHitWallTangentAngle(float hitX, float hitY, float hitH, float dir, float* rsTangentDir) 
{
    // hitX hitY 为墙上的点
	MMO_ASSERT(rsTangentDir != nullptr);
	MMO_ASSERT(m_navMesh != nullptr);

    bool result = false;
    bool retCode = false;

    dtPolyRef startRef = INVALID_NAVMESH_POLYREF;
    dtStatus status;
    float nearPos[3] = {0.0f};
    float centerPos[3] = {0.0f};
    float endPos[3] = {0.0f};
    float hitNormal[3] = {0.0f};
    float t = 0.0f;
    int polyCount = 0;
    float radian = 0.0f;
    Point2<float> offsetForward;
    const dtMeshTile* tile = nullptr;
    const dtPoly* poly = nullptr;
	Point2<float> from;
	Point2<float> to;

    retCode = FindNearestPos(hitX, hitY, hitH, &nearPos[0], &nearPos[2], &nearPos[1], &startRef);
	MMOLOG_PROCESS_ERROR(retCode);

    m_navMesh->getTileAndPolyByRefUnsafe(startRef, &tile, &poly);
    dtCalcPolyCenter(centerPos, poly->verts, poly->vertCount, tile->verts);

	from.x = centerPos[0];
	from.y = centerPos[2];
	to.x = hitX;
	to.y = hitY;
	//Point2<float> from = { centerPos[0], centerPos[2] };
	//Point2<float> to = { hitX, hitY };
	radian = AngleToRadian(ReviseDir(Angle2D(from, to)));
    FormulaPos(radian, 1.5f, &offsetForward);
    dtVset(endPos, hitX + offsetForward.x, 0.0f, hitY + offsetForward.y);

    status = m_navMeshQuery->raycast(startRef, centerPos, endPos, &m_normalFilter,
        &t, hitNormal, m_pathPolys, &polyCount, MAX_NAV_POLYS);

	MMOLOG_PROCESS_ERROR(dtStatusSucceed(status));

	MMO_PROCESS_ERROR(t > 0.0f && t < 1.0f);

    // 计算碰撞切线方向
    {
        float tangenDir = 0.0f;
		from.x = 0;
		from.y = 0;
		to.x = hitNormal[0];
		to.y = hitNormal[2];
	/*	Point2<float> from1 = { 0, 0 };
		Point2<float> to1 = { hitNormal[0], hitNormal[2] };*/
		float normalDir = Angle2D(from, to); // 碰撞法线  
        float detaDir = DeltaAngle(normalDir, dir); // 当前方向和法线的夹角

        if (detaDir > 90.0f && detaDir <= 180.0f) 
		{
            tangenDir = ReviseDir(dir - (detaDir - 90.0f) - 2.0f);
        }
        else if (detaDir > 180.0f && detaDir <= 270.0f) 
		{
            tangenDir = ReviseDir(dir + (270.0f - detaDir) + 2.0f);
        }

        *rsTangentDir = tangenDir;
    }

    result = true;
Exit0:
    return result;
}

bool Map::GetPolyHeight(dtPolyRef polyRef, float posX, float posY, float* height) 
{
	MMO_ASSERT(height != nullptr);

    bool result = false;

    float h = 0;
    float pos[3] = {posX, 0.0f, posY};

    dtStatus status = m_navMeshQuery->getPolyHeight(polyRef, pos, &h);
	MMO_PROCESS_ERROR(dtStatusSucceed(status));

    if (height != nullptr) 
	{
        *height = h;
    }

    result = true;
Exit0:
    return result;
}

bool Map::CheckPolyFlag(dtPolyRef polyRef, SamplePolyFlags flag) 
{
    bool result = false;
    unsigned short flags = 0;

    dtStatus status = m_navMesh->getPolyFlags(polyRef, &flags);
	MMOLOG_PROCESS_ERROR(dtStatusSucceed(status));

	MMO_PROCESS_ERROR(flags & flag);

    result = true;
Exit0:
    return result;
}

void Map::GetPolyFlags(dtPolyRef ref, uint_least16_t* flags)
{
	m_navMesh->getPolyFlags(ref, flags);
}

bool	  Map::IsInSafeArea(Point3<float> & nPos)
{
	//先找到该地图上的安全区域
	std::vector<uint32_t> vID;
	g_GetMapLocationCfg()->GetAreaIdByMap(m_pMapCfg->mapId, vID);
	for (std::vector<uint32_t>::iterator iter = vID.begin(); iter != vID.end(); ++iter)
	{
		const AreaAreaCfgInfo * pArea = g_GetAreaAreaCfgTable()->GetAreaAreaCfgInfo(*iter);
		if (pArea)
		{
			if (pArea->safeAreaType >= en_AreaType_Neutrality)
			{
				if (g_GetMapLocationCfg()->InArea(*iter, nPos))
				{
					return true;
				}
			}
		}
	}
	return false;
}
